
import asyncio
import os
import random
import time
from datetime import timezone, timedelta, datetime
from os import system

from pathlib import Path
import serial_asyncio
import subprocess

from common.third_party.devmem import DevMem
from services.ad936x.schemas.configs import DoubleConfig, SingleConfig, SingleFrequency, DoubleFrequency, DoubleGain, \
    SingleGain, DoubleAttenuation, SingleAttenuation
from common.third_party.save_config import save_config, load_config
from services.dsp.api import DspApi, DspConfig
from services.control.api import ControlApi
from services.server_integration.api import ServerIntegrationApi
from settings import settings


class Ad936x:
    __slots__ = ['configs', 'statuses', 'night_digital_tune', 'digital_tune_range', '_bandwidths']

    AD936X_PATH: str = '/sys/bus/iio/devices/iio:device'
    MODE: str = 'manual'
    IP_CORE_0_ADDRESS: int = 0x83C7_4400
    IP_CORE_1_ADDRESS: int = 0x83C8_4400
    IP_CORE_2_ADDRESS: int = 0x83C9_4400
    IP_CORE_3_ADDRESS: int = 0x83CA_4400

    CTRL_API = ControlApi()
    SI_API = ServerIntegrationApi()

    def __init__(self) -> None:
        # with open(f'{settings.ROOT_PATH}/environment/configs/ad/devices_list.txt', 'a+') as file:
        #     device_list = self.get_device_list()
        #     file.write(f'{len(device_list)} -- {self.get_device_list()}\n')

        status = load_config('ad/reboot_status2', int, 0)
        if self.check_ads():
            status = 0
        else:
            status += 1
        save_config('ad/reboot_status2', status)

        if 1 <= status <= 4:
            os.system('sudo reboot')

        self.statuses = [
            self.ad_is_loaded(i)
            for i in range(4)
        ]

        self.night_digital_tune = load_config('ad/night_digital_tune', bool, True)
        self.digital_tune_range = load_config('ad/digital_tune_range', list[tuple[int, int, int, int]], [
            (0, 0, 0xf, 0xf),
            (0, 0, 0xf, 0xf),
            (0, 0, 0xf, 0xf),
            (0, 0, 0xf, 0xf)
        ])

        self._bandwidths = load_config('ad/bandwidths', list[int], [30_000_000] * 4)

        self.configs: list[DoubleConfig | SingleConfig] = []
        for i in range(settings.AD936X_NUMBER):
            if self.check_double(i):
                config = load_config(f'ad/config_{i}', DoubleConfig, DoubleConfig(double=True))
            else:
                config = load_config(f'ad/config_{i}', SingleConfig, SingleConfig(double=False, attenuation_0=70, gain_0=15))

            config.sample_rate = load_config(f'ad/sample_rate', float, 60_000_000)
            config.tx_bandwidth = 56_000_000
            self.configs.append(config)

        save_config(f'ad/sample_rate', 60_000_000)
        if self.configs[0].sample_rate != 60_000_000:
            os.system('sudo reboot')

        if not settings.DEBUG:
            self.configure_ip_cores(self.IP_CORE_0_ADDRESS)
            self.configure_ip_cores(self.IP_CORE_1_ADDRESS)
            self.configure_ip_cores(self.IP_CORE_2_ADDRESS)
            self.configure_ip_cores(self.IP_CORE_3_ADDRESS)

            for idx, config in enumerate(self.configs):
                if config.double:
                    self.write_gain_mode(idx, 0, self.MODE)
                    self.write_gain_mode(idx, 1, self.MODE)
                else:
                    self.write_gain_mode(idx, 0, self.MODE)

                self.configure(idx, config, True)

        asyncio.create_task(self.write_filter_fir_config())

    def ad_is_loaded(self, index):
        def read_name(idx: int) -> str:
            try:
                with open(f'{self.AD936X_PATH}{idx}/name', 'r') as file:
                    name = file.readline().rstrip()
                    return name
            except Exception as e:
                return 'invalid'

        ad_name = ('ad9361-phy0', 'ad9361-phy1', 'ad9361-phy2', 'ad9364-phy')[index]
        current_names = [read_name(idx) for idx in range(4)]

        return ad_name in current_names

    async def digital_tune(self, index: int):
        pass
        # fd = None
        # try:
        #     fd = os.open(f'/sys/kernel/debug/iio/iio:device{index}/digital_tune', os.O_RDWR)
        #     os.write(fd, bytes('0 3', encoding='utf-8'))
        # except Exception as e:
        #     if not load_config(f'ad/digital_tune_reboot_{index}', bool, False):
        #         save_config(f'ad/digital_tune_reboot_{index}', True)
        #         save_config(f'ad/sample_rate', 61_440_000)
        #         os.system('sudo reboot')
        # else:
        #     save_config(f'ad/digital_tune_reboot_{index}', False)
        # finally:
        #     if fd is not None:
        #         os.close(fd)

    async def digital_tune_all(self):
        for ad_index in range(3):
            await self.digital_tune_one(ad_index)

        await self.bist_timing_analysis(4)

    async def digital_tune_one(self, ad_index, check_flag = False):
        dsp_api = DspApi()
        link_ul_scale = await dsp_api.get_dsp_config('LINK_UL_SCALE')
        # services_tx_scale = dsp_api.get_dsp_config('SERVICE_TX_SCALE')
        await dsp_api.set_dsp_config('LINK_UL_SCALE', DspConfig(), do_reinit=False)
        # dsp_api.set_dsp_config('SERVICE_TX_SCALE', DspConfig(), do_reinit=False)

        self.write_gain(ad_index, 0, 10)
        self.write_gain(ad_index, 1, 10)
        self.write_attenuation(ad_index, 0, 60)
        self.write_attenuation(ad_index, 1, 60)

        # for i in range(4):
        #     await self.digital_tune(i)

        await asyncio.sleep(0.5)

        await self.bist_timing_analysis(ad_index, check_flag)

        gain_0, gain_1 = self.configs[ad_index].gain_0, self.configs[ad_index].gain_1
        attenuation_0, attenuation_1 = self.configs[ad_index].attenuation_0, self.configs[ad_index].attenuation_1
        self.write_gain(ad_index, 0, gain_0)
        self.write_gain(ad_index, 1, gain_1)
        self.write_attenuation(ad_index, 0, attenuation_0)
        self.write_attenuation(ad_index, 1, attenuation_1)

        await dsp_api.set_dsp_config('LINK_UL_SCALE', link_ul_scale, do_reinit=False)
        # dsp_api.set_dsp_config('SERVICE_TX_SCALE', services_tx_scale, do_reinit=False)

    async def dcdc_loop(self):
        async def write_msg1():
            reader, writer = await asyncio.wait_for(serial_asyncio.open_serial_connection(
                loop=asyncio.get_event_loop(),
                url='/dev/ttyUL3', baudrate=38400,
                timeout=1
            ), 3.0)
            writer.write(b'\xAA\xBB\xCC\x01')
            await writer.drain()
            writer.close()

        while True:
            try:
                await write_msg1()
            except Exception:
                await asyncio.sleep(13.3)

            await asyncio.sleep(1.4 * 60)

    async def repare_ad9364_loop(self):
        while True:
            await asyncio.sleep(60)
            try:
                with open(f'{settings.ROOT_PATH}/environment/dma/invalid_ad9364', 'rt') as file:
                    invalid_count = int(file.readline().rstrip())

                with open(f'{settings.ROOT_PATH}/environment/dma/invalid_ad9364', 'wt') as file:
                    file.write("0")

            except Exception as e:
                    invalid_count = 0

            if invalid_count > 200:
                fd = os.open(f'/sys/kernel/debug/iio/iio:device3/initialize', os.O_RDWR)
                os.write(fd, bytes('1', encoding='utf-8'))
                os.close(fd)

                self.configure_ip_cores(self.IP_CORE_3_ADDRESS)
                self.write_gain_mode(3, 0, self.MODE)
                self.configure(3, self.configs[3], True)


    async def digital_tune_loop(self):
        # await asyncio.sleep(60)
        # await self.digital_tune_all()
        await asyncio.sleep(8 * 60)

        for index in range(6):
            save_config(f'ad/invalid_ndt_samples-{index}', False)

        zeroing_task = [False]
        dg_tune_count = [0]

        async def zeroing():
            await asyncio.sleep(60 * 60)
            dg_tune_count[0] = 0
            zeroing_task[0] = False

        while True:
            await asyncio.sleep(60.4)
            try:
                tz = await self.SI_API.get_timezone()
                tz = timezone(timedelta(hours=tz))
                tm = datetime.now(tz)
                # if load_config(f'ad/invalid_ndt_samples', bool, False) or (self.night_digital_tune and tm.hour == 2 and tm.minute < 1.08):
                # if load_config(f'ad/invalid_ndt_samples', bool, False):

                invalid_ndt_samples = [load_config(f'ad/invalid_ndt_samples-{i}', bool, False) for i in range(6)]
                clipping = self._read_clipping()
                for _ in range(5):
                    await asyncio.sleep(5)
                    clipping = [ea or eb for ea, eb in zip(clipping, self._read_clipping())]

                new_invalid_ndt_samples = [
                    (invalid_ndt_samples[0] or invalid_ndt_samples[1]) and not(clipping[0] and clipping[1]),
                    (invalid_ndt_samples[2] or invalid_ndt_samples[3]) and not(clipping[2] and clipping[3]),
                    (invalid_ndt_samples[4] or invalid_ndt_samples[5]) and not(clipping[4] and clipping[5]),
                ]
                for index, invalid_ndt in enumerate(new_invalid_ndt_samples):
                    if invalid_ndt:
                        if not zeroing_task[0]:
                            asyncio.create_task(zeroing())
                            zeroing_task[0] = True

                        ndti0 = index * 2
                        ndti1 = ndti0 + 1

                        if dg_tune_count[0] > 24:
                            if not(tm.hour == 2 and tm.minute < 2.08):
                                break

                            await self.SI_API.raise_info('DigitalTuneReboot', True, False)
                            save_config('ad/digital_tune_reboot_time', tm.timestamp())
                            os.system('reboot')

                        await self.digital_tune_one(index, True)
                        if index == 0 and self.configs[0].gain_0 < 6:
                            self.write_gain(0, 0, 15)
                        if index == 0 and self.configs[0].gain_1 < 6:
                            self.write_gain(0, 1, 15)

                        dg_tune_count[0] += 1
                        await asyncio.sleep(5.0)

                        if invalid_ndt_samples[ndti0]:
                            await self.SI_API.raise_info(f'DigitalTuneD{ndti0}', True, False)
                            save_config(f'ad/invalid_ndt_samples-{ndti0}', False),

                        if invalid_ndt_samples[ndti1]:
                            await self.SI_API.raise_info(f'DigitalTuneD{ndti1}', True, False)
                            save_config(f'ad/invalid_ndt_samples-{ndti1}', False),

                    await asyncio.sleep(3.0)

            except Exception as e:
                pass

    def configure(self, index: int, config: DoubleConfig | SingleConfig, on_init: bool = False):
        def write(func, name):
            value = getattr(config, name)
            prev = getattr(self.configs[index], name)
            if on_init or value != prev:
                func(index, value)

        def write_channel(func, name, channel):
            value = getattr(config, name)
            prev = getattr(self.configs[index], name)
            if on_init or value != prev:
                func(index, channel, value)

        if not settings.DEBUG:
            write(self.write_rx_frequency, 'rx_frequency')
            write_channel(self.write_gain, 'gain_0', 0)
            write_channel(self.write_attenuation, 'attenuation_0', 0)
            write(self.write_rx_bandwidth, 'rx_bandwidth')
            write(self.write_rx_samplerate, 'sample_rate')

            if config.double:
                write(self.write_tx_frequency, 'tx_frequency')
                write_channel(self.write_gain, 'gain_1', 1)
                write_channel(self.write_attenuation, 'attenuation_1', 1)
                write(self.write_tx_bandwidth, 'tx_bandwidth')
                write(self.write_tx_samplerate, 'sample_rate')

        self.configs[index] = config
        save_config(f'ad/config_{index}', config)

    def configure_center_frequency(self, index: int, config: DoubleFrequency | SingleFrequency):
        if not settings.DEBUG:
            self.write_rx_frequency(index, config.rx_frequency)
            if config.double:
                self.write_tx_frequency(index, config.tx_frequency)

        self.configs[index].rx_frequency = config.rx_frequency
        if config.double:
            self.configs[index].tx_frequency = config.tx_frequency

        save_config(f'ad/config_{index}', self.configs[index])

    def configure_gain(self, index: int, config: DoubleGain | SingleGain):
        if not settings.DEBUG:
            self.write_gain(index, 0, config.gain_0)
            if config.double:
                self.write_gain(index, 1, config.gain_1)

        self.configs[index].gain_0 = config.gain_0
        if config.double:
            self.configs[index].gain_1 = config.gain_1

        save_config(f'ad/config_{index}', self.configs[index])

    def configure_attenuation(self, index: int, config: DoubleAttenuation | SingleAttenuation):
        if not settings.DEBUG:
            self.write_attenuation(index, 0, config.attenuation_0)
            if config.double:
                self.write_attenuation(index, 1, config.attenuation_1)

        self.configs[index].attenuation_0 = config.attenuation_0
        if config.double:
            self.configs[index].attenuation_1 = config.attenuation_1

        save_config(f'ad/config_{index}', self.configs[index])

    def write_rx_frequency(self, index: int = 0, frequency: int = 1_750_000_000):
        with open(f'{self.AD936X_PATH}{index}/out_altvoltage0_RX_LO_frequency', 'r+', encoding='utf8') as file:
            prev = int(file.read().strip())
            if frequency != prev:
                file.write(str(frequency))

    def write_tx_frequency(self, index: int = 0, frequency: int = 1_330_000_000):
        with open(f'{self.AD936X_PATH}{index}/out_altvoltage1_TX_LO_frequency', 'r+', encoding='utf8') as file:
            prev = int(file.read().strip())
            if frequency != prev:
                file.write(str(frequency))

    def write_gain(self, index: int = 0, channel: int = 0, gain: int = 0):
        fd = os.open(f'{self.AD936X_PATH}{index}/in_voltage{channel}_hardwaregain', os.O_RDWR)
        os.write(fd, bytes(str(gain), encoding='utf8'))
        os.close(fd)

    def write_attenuation(self, index: int = 0, channel: int = 0, attenuation: float = 0):
        fd = os.open(f'{self.AD936X_PATH}{index}/out_voltage{channel}_hardwaregain', os.O_RDWR)
        os.write(fd, bytes(str(-attenuation), encoding='utf8'))
        os.close(fd)

    def write_rx_bandwidth(self, index: int = 0, bandwidth: int = 56_000_000):
        fd = os.open(f'{self.AD936X_PATH}{index}/in_voltage_rf_bandwidth', os.O_RDWR)
        os.write(fd, bytes(str(bandwidth), encoding='utf8'))
        os.close(fd)

    def write_tx_bandwidth(self, index: int = 0, bandwidth: int = 56_000_000):
        fd = os.open(f'{self.AD936X_PATH}{index}/out_voltage_rf_bandwidth', os.O_RDWR)
        os.write(fd, bytes(str(bandwidth), encoding='utf8'))
        os.close(fd)

    def write_rx_samplerate(self, index: int = 0, sample_rate: int = 60_000_000):
        fd = os.open(f'{self.AD936X_PATH}{index}/in_voltage_sampling_frequency', os.O_RDWR)
        os.write(fd, bytes(str(sample_rate), encoding='utf8'))
        os.close(fd)

    def write_tx_samplerate(self, index: int = 0, sample_rate: int = 60_000_000):
        fd = os.open(f'{self.AD936X_PATH}{index}/out_voltage_sampling_frequency', os.O_RDWR)
        os.write(fd, bytes(str(sample_rate), encoding='utf8'))
        os.close(fd)

    def check_double(self, index: int = 0) -> bool:
        tx_center_frequency = Path(f'{self.AD936X_PATH}{index}/out_altvoltage1_TX_LO_frequency')
        rx_gain_1 = Path(f'{self.AD936X_PATH}{index}/in_voltage1_hardwaregain')
        tx_attenuation_1 = Path(f'{self.AD936X_PATH}{index}/out_voltage1_hardwaregain')
        if tx_center_frequency.exists() and rx_gain_1.exists() and tx_attenuation_1.exists():
            return True
        return False

    def get_real_uldl(self, index) -> tuple[float, float]:
        ad_index = index // 2
        channel = index % 2
        try:
            with open(f'{self.AD936X_PATH}{ad_index}/in_voltage{channel}_hardwaregain') as gain_file, open(f'{self.AD936X_PATH}{ad_index}/out_voltage{channel}_hardwaregain') as attenuation_file:
                gain = float(gain_file.read().strip().split(' ')[0])
                attenuation = float(attenuation_file.read().strip().split(' ')[0]) * -1.0
                return gain, attenuation
        except Exception as e:
            return 0, 0

    async def direct_write_bandwidth(self, index: int, bw: int = None):
        if bw is not None:
            self._bandwidths[index] = bw
            save_config('ad/bandwidths', self._bandwidths)
        else:
            bw = self._bandwidths[index]

        block_state = await self.CTRL_API.get_control_state('block_state')
        if index != 0 and block_state:
            with open(f'{self.AD936X_PATH}{index}/in_voltage_filter_fir_en', 'w') as in_voltage_filter_fir_en:
                in_voltage_filter_fir_en.write('0')

        bw = round(bw / 1_000_000) * 1_000_000

        self.write_rx_bandwidth(index, bw)
        # self.write_tx_bandwidth(index, bw)
        self.configs[index].rx_bandwidth = bw
        # self.configs[index].tx_bandwidth = bw

        return

        # if index in (1, 2):
        #
        #     file_name = {
        #         5_000_000: '10MHz-1.ftr',
        #         10_000_000: '10MHz-1.ftr',
        #         15_000_000: '15MHz-1.ftr',
        #         20_000_000: '20MHz-1.ftr',
        #         30_000_000: '30MHz-1.ftr',
        #     }.get(bw, '30MHz-1.ftr')
        #
        #     with open(f'{settings.APP_PATH}/services/dsp/coe_files/{file_name}', 'r') as ad_fir_coe,\
        #           open(f'{self.AD936X_PATH}{index}/filter_fir_config', 'w') as filter_fir_config:
        #         filter_fir_config.write(ad_fir_coe.read())
        #
        #     sm_a0 = False
        #     sm_a1 = False
        #     if index == 1:
        #         if self.CTRL_API.get_control_state('service_0_state'):
        #             sm_a0 = True
        #             self.CTRL_API.set_control_state('service_0_state', False)
        #         if self.CTRL_API.get_control_state('service_1_state'):
        #             sm_a1 = True
        #             self.CTRL_API.set_control_state('service_1_state', False)
        #     elif index == 2:
        #         if self.CTRL_API.get_control_state('service_2_state'):
        #             sm_a0 = True
        #             self.CTRL_API.set_control_state('service_2_state', False)
        #         if self.CTRL_API.get_control_state('service_3_state'):
        #             sm_a1 = True
        #             self.CTRL_API.set_control_state('service_3_state', False)
        #
        #     time.sleep(1)
        #
        #     with open(f'{self.AD936X_PATH}{index}/in_voltage_filter_fir_en', 'w') as in_voltage_filter_fir_en:
        #         in_voltage_filter_fir_en.write('0')
        #
        #     time.sleep(1)
        #
        #     with open(f'{self.AD936X_PATH}{index}/in_voltage_filter_fir_en', 'w') as in_voltage_filter_fir_en:
        #         in_voltage_filter_fir_en.write('1')
        #
        #     time.sleep(1.5)
        #
        #     if index == 1:
        #         if sm_a0:
        #             self.CTRL_API.set_control_state('service_0_state', True)
        #         if sm_a1:
        #             self.CTRL_API.set_control_state('service_1_state', True)
        #     elif index == 2:
        #         if sm_a0:
        #             self.CTRL_API.set_control_state('service_2_state', True)
        #         if sm_a1:
        #             self.CTRL_API.set_control_state('service_3_state', True)

    async def write_filter_fir_config(self):
        await asyncio.sleep(15)
        block_state = await self.CTRL_API.get_control_state('block_state')
        if not settings.DEBUG:
            for index in range(3):
                if index == 0:
                    with open(f'{settings.APP_PATH}/services/dsp/coe_files/ad0_fir_coe.txt',
                              'r') as ad_fir_coe, open(f'{self.AD936X_PATH}{index}/filter_fir_config',
                                                        'w') as filter_fir_config:
                        filter_fir_config.write(ad_fir_coe.read())
                # else:
                #     bw = round(self._bandwidths[index] / 1_000_000) * 1_000_000
                #     file_name = {
                #         5_000_000: '10MHz-1.ftr',
                #         10_000_000: '10MHz-1.ftr',
                #         15_000_000: '15MHz-1.ftr',
                #         20_000_000: '20MHz-1.ftr',
                #         30_000_000: '30MHz-1.ftr',
                #     }.get(bw, '30MHz-1.ftr')
                #     with open(f'{settings.APP_PATH}/services/dsp/coe_files/{file_name}',
                #               'r') as ad_fir_coe, open(f'{self.AD936X_PATH}{index}/filter_fir_config',
                #                                         'w') as filter_fir_config:
                #         filter_fir_config.write(ad_fir_coe.read())

                if index == 0 or not block_state:
                    if index == 0:
                        with open(f'{self.AD936X_PATH}{index}/out_voltage_filter_fir_en', 'w') as out_voltage_filter_fir_en:
                            out_voltage_filter_fir_en.write('1')

                        with open(f'{self.AD936X_PATH}{index}/in_voltage_filter_fir_en', 'w') as in_voltage_filter_fir_en:
                            in_voltage_filter_fir_en.write('1')

                    time.sleep(2.1)
                    self.write_rx_samplerate(index, self.configs[index].sample_rate)
                    self.write_tx_samplerate(index, self.configs[index].sample_rate)

                # time.sleep(1)

    @staticmethod
    def check_binding() -> bool:
        dev_names = [f'iio:device{x}' for x in range(12)]
        dir_list = os.listdir('/sys/bus/iio/devices/')

        list(set(dev_names) - set(dir_list))

        if len(list(set(dev_names) - set(dir_list))) == 0:
            return True
        return False

    @staticmethod
    def reset() -> None:
        if not settings.DEBUG:
            os.system('reboot')

    @staticmethod
    def configure_ip_cores(address: int):
        mem = DevMem(address=address, number_of_words=0xD8)
        mem.write(offset=0x18, value=2)
        mem.write(offset=0x58, value=2)
        mem.write(offset=0x98, value=2)
        mem.write(offset=0xD8, value=2)

    def write_gain_mode(self, index: int = 0, channel: int = 0, mode: str = 'manual'):
        fd = os.open(f'{self.AD936X_PATH}{index}/in_voltage{channel}_gain_control_mode', os.O_RDWR)
        os.write(fd, bytes(str(mode), encoding='utf8'))
        os.close(fd)

    def check_ads(self):
        # return os.path.exists('/sys/bus/iio/devices/iio:device9/buffer')
        return len(os.listdir('/sys/bus/iio/devices')) == 13

    def get_device_list(self) -> list[str]:
        def read_name(idx: int) -> str:
            try:
                with open(f'{self.AD936X_PATH}{idx}/name', 'r') as file:
                    name = file.readline().rstrip()
                    return name
            except Exception as e:
                return 'invalid'

        device_count = len(os.listdir('/sys/bus/iio/devices'))
        return [read_name(index) for index in range(device_count)]

    async def bist_timing_analysis(self, index: int, check_flag = False):
        try:
            def find_timing_position(bits_timing):
                bits_timing = list(
                    map(lambda line: line[2:].replace(' ', ''),
                        filter(lambda line: len(line) == 34,
                               bits_timing.split('\n')[2:])))
                mtx = []
                mtx_range = self.digital_tune_range[index]
                for line in bits_timing:
                    mtx_line = list(map(lambda bit: bit == 'o', line))
                    mtx.append(mtx_line[mtx_range[0]: mtx_range[2]+1])

                mtx = mtx[mtx_range[1]: mtx_range[3]+1]
                height = len(mtx)
                width = len(mtx[0])

                loc = []
                for x in range(width - 3):
                    for y in range(height - 3):
                        score = 0
                        for lx in range(4):
                            for ly in range(4):
                                if mtx[y + ly][x + lx]:
                                    score += 1
                        if score > 6:
                            loc.append((x, y, score))
                try:
                    max_loc = max(loc, key=lambda x: x[2])

                    res = max_loc[0] + 2 + mtx_range[0], max_loc[1] + 2 + mtx_range[1]
                    return res
                except Exception as e:
                    return None

            def fetch_bist_timing_analysis():
                fd = os.open(f'/sys/kernel/debug/iio/iio:device{index}/bist_timing_analysis', os.O_RDWR)
                os.write(fd, bytes('1', encoding='utf-8'))
                os.close(fd)
                fd = os.open(f'/sys/kernel/debug/iio/iio:device{index}/bist_timing_analysis', os.O_RDWR)
                bits_timing_analysis = os.read(fd, 10000)
                os.close(fd)

                return bits_timing_analysis

            def write_timing(tcol, trow):
                try:
                    fd = os.open(f'/sys/kernel/debug/iio/iio:device{index}/direct_reg_access', os.O_RDWR)
                    os.write(fd, bytes(f'0x6 0x{tcol:x}{trow:x}', encoding='utf-8'))
                    os.close(fd)
                except Exception as e:
                    pass

            bits_timing_analysis = fetch_bist_timing_analysis()
            invalid_ad = False
            try:
                bits_timing_analysis = bits_timing_analysis.decode('utf-8')
            except Exception as e:
                invalid_ad = True

            invalid_ad = invalid_ad or not('DC0:1:2:3:4:5:6:7:8:9:a:b:c:d:e:f' in bits_timing_analysis)

            # if invalid_ad:
            #     write_timing(random.randint(0, 15), random.randint(0, 15))
            #
            #     # Не помогает
            #     # if not load_config(f'ad/digital_tune_reboot_{index}', bool, False):
            #     #     self.SI_API.raise_info(f'FaultBistTimingAnalysisD{index}', True, False)
            #     #     save_config(f'ad/digital_tune_reboot_{index}', True)
            #     #     save_config(f'ad/sample_rate', 61_440_000)
            #     #     os.system('sudo reboot')
            # else:
            #     save_config(f'ad/digital_tune_reboot_{index}', False)

            def set_timing(col_offset, row_offset):
                if invalid_ad:
                    timing_position = (random.randint(0, 10), random.randint(4, 15))
                else:
                    timing_position = find_timing_position(bits_timing_analysis)
                    if timing_position is None:
                        timing_position = (random.randint(0, 10), random.randint(4, 15))

                col, row = timing_position
                write_timing(col + col_offset, row + row_offset)

            if not check_flag:
                set_timing(0, 0)
                return

            for i in range(1, 8):
                ndti0 = index * 2
                ndti1 = ndti0 + 1

                scale = i // 2
                co = ((i+1) % 2) * scale
                ro = ((i+0) % 2) * scale
                set_timing(co, ro)

                await asyncio.sleep(2.0)
                save_config(f'ad/invalid_ndt_samples-{ndti0}', False),
                save_config(f'ad/invalid_ndt_samples-{ndti1}', False),
                await asyncio.sleep(6.0)

                if not(load_config(f'ad/invalid_ndt_samples-{ndti0}', bool, False) or load_config(
                        f'ad/invalid_ndt_samples-{ndti1}', bool, False)):
                    return
        except Exception:
            pass

        # if index in (1, 2):
        #     self.direct_write_bandwidth(index)

    def _read_clipping(self) -> list[bool]:
        try:
            with open('/home/debian/rcs/environment/dma/clipping', "rt") as clipping:
                data = clipping.read().split('\n')
                return [
                    int(data[6].strip()) > 2 and self.configs[0].gain_0 > 5,
                    int(data[5].strip()) > 2 and self.configs[0].gain_1 > 5,
                    int(data[0].strip()) > 2 and self.configs[1].gain_1 > 3,
                    int(data[1].strip()) > 2 and self.configs[1].gain_0 > 3,
                    int(data[2].strip()) > 2 and self.configs[2].gain_1 > 3,
                    int(data[3].strip()) > 2 and self.configs[2].gain_0 > 3,
                ]
        except:
            return [False] * 8

    async def repare_dma_loop(self):
        while True:
            await asyncio.sleep(85)
            if load_config('ac/dma_off', bool, False):
                continue

            is_drop = False
            try:
                res = subprocess.run(['pidof', 'radius_control_software_dma_server'],
                                           capture_output=True)
                is_drop = len(res.stdout) < 3
            except Exception as e:
                is_drop = True

            if is_drop:
                system('systemctl restart rcs-dma')
                system('systemctl restart rcs-automatic-control')